// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtTest
import QtQuick.Controls

TestCase {
    id: testCase
    width: 400
    height: 400
    visible: true
    when: windowShown
    name: "RangeSlider"

    Component {
        id: signalSpy
        SignalSpy { }
    }

    Component {
        id: sliderComponent
        RangeSlider {
            id: slider

            Component.onCompleted: {
                first.handle.objectName = "firstHandle"
                second.handle.objectName = "secondHandle"
            }

            Text {
                text: "1"
                parent: slider.first.handle
                anchors.centerIn: parent
            }

            Text {
                text: "2"
                parent: slider.second.handle
                anchors.centerIn: parent
            }
        }
    }

    function init() {
        failOnWarning(/.?/)
    }

    function test_defaults() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.stepSize, 0)
        compare(control.snapMode, RangeSlider.NoSnap)
        compare(control.orientation, Qt.Horizontal)
        compare(control.horizontal, true)
        compare(control.vertical, false)
    }

    function test_values() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.first.value, 0.0)
        compare(control.second.value, 1.0)
        control.first.value = 0.5
        compare(control.first.value, 0.5)
        control.first.value = 1.0
        compare(control.first.value, 1.0)
        control.first.value = -1.0
        compare(control.first.value, 0.0)
        control.first.value = 2.0
        compare(control.first.value, 1.0)

        control.first.value = 0
        compare(control.first.value, 0.0)
        control.second.value = 0.5
        compare(control.second.value, 0.5)
        control.first.value = 1
        compare(control.first.value, 0.5)
        control.second.value = 0
        compare(control.second.value, 0.5)
    }

    function test_range() {
        let control = createTemporaryObject(sliderComponent, testCase, { from: 0, to: 100, "first.value": 50, "second.value": 100 })
        verify(control)

        compare(control.from, 0)
        compare(control.to, 100)
        compare(control.first.value, 50)
        compare(control.second.value, 100)
        compare(control.first.position, 0.5)
        compare(control.second.position, 1.0)

        control.first.value = 1000
        compare(control.first.value, 100)
        compare(control.first.position, 1.0)

        control.first.value = -1
        compare(control.first.value, 0)
        compare(control.first.position, 0)

        control.from = 25
        compare(control.from, 25)
        compare(control.first.value, 25)
        compare(control.first.position, 0)

        control.to = 75
        compare(control.to, 75)
        compare(control.second.value, 75)
        compare(control.second.position, 1.0)

        control.first.value = 50
        compare(control.first.value, 50)
        compare(control.first.position, 0.5)
    }

    function test_setToFromUpdatesHandles() {
        let control = createTemporaryObject(sliderComponent, testCase, { from: 0, to: 100, "first.value": 50, "second.value": 75 })
        verify(control)

        let firstPos = control.first.position
        let secondPos = control.second.position

        let firstPosChangesSpy = signalSpy.createObject(control, {target: control.first, signalName: "positionChanged"})
        verify(firstPosChangesSpy.valid)

        let secondPosChangesSpy = signalSpy.createObject(control, {target: control.second, signalName: "positionChanged"})
        verify(secondPosChangesSpy.valid)

        // Increasing the 'to' value, so the positions of the handles should be
        // moved to the left (become smaller)
        control.to = 200;
        compare(firstPosChangesSpy.count, 1)
        compare(secondPosChangesSpy.count, 1)
        verify(control.first.position < firstPos)
        verify(control.second.position < secondPos)

        // resetting the values
        control.to = 100
        firstPosChangesSpy.clear()
        secondPosChangesSpy.clear()
        firstPos = control.first.position
        secondPos = control.second.position

        // Decreasing the 'from' value, so the positions of the handles should
        // be moved to the right (become larger)
        control.from = -100
        compare(firstPosChangesSpy.count, 1)
        compare(secondPosChangesSpy.count, 1)
        verify(control.first.position > firstPos)
        verify(control.second.position > secondPos)
    }

    function test_setValues() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.from, 0)
        compare(control.to, 1)
        compare(control.first.value, 0)
        compare(control.second.value, 1)
        compare(control.first.position, 0.0)
        compare(control.second.position, 1.0)

        control.setValues(100, 200)
        compare(control.first.value, 1)
        compare(control.second.value, 1)
        compare(control.first.position, 1.0)
        compare(control.second.position, 1.0)

        control.to = 300;
        control.setValues(100, 200)
        compare(control.first.value, 100)
        compare(control.second.value, 200)
        compare(control.first.position, 0.333333)
        compare(control.second.position, 0.666666)
    }

    function test_inverted() {
        let control = createTemporaryObject(sliderComponent, testCase, { from: 1.0, to: -1.0 })
        verify(control)

        compare(control.from, 1.0)
        compare(control.to, -1.0)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.5)
        compare(control.second.value, 0.0);
        compare(control.second.position, 0.5);

        control.first.value = 2.0
        compare(control.first.value, 1.0)
        compare(control.first.position, 0.0)
        compare(control.second.value, 0.0);
        compare(control.second.position, 0.5);

        control.first.value = -2.0
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.5)
        compare(control.second.value, 0.0);
        compare(control.second.position, 0.5);

        control.first.value = 0.0
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.5)
        compare(control.second.value, 0.0);
        compare(control.second.position, 0.5);
    }

    function test_visualPosition() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.first.visualPosition, 0.0)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, 1.0)

        control.first.value = 0.25
        compare(control.first.value, 0.25)
        compare(control.first.position, 0.25)
        compare(control.first.visualPosition, 0.25)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, 1.0)

        // RTL locale
        control.locale = Qt.locale("ar_EG")
        compare(control.first.visualPosition, 0.25)
        compare(control.second.visualPosition, 1.0)

        // RTL locale + LayoutMirroring
        control.LayoutMirroring.enabled = true
        compare(control.first.visualPosition, 0.75)
        compare(control.second.visualPosition, 0.0)

        // LTR locale + LayoutMirroring
        control.locale = Qt.locale("en_US")
        compare(control.first.visualPosition, 0.75)
        compare(control.second.visualPosition, 0.0)

        // LTR locale
        control.LayoutMirroring.enabled = false
        compare(control.first.visualPosition, 0.25)
        compare(control.second.visualPosition, 1.0)

        // LayoutMirroring
        control.LayoutMirroring.enabled = true
        compare(control.first.visualPosition, 0.75)
        compare(control.second.visualPosition, 0.0)
    }

    function test_orientation() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.orientation, Qt.Horizontal)
        compare(control.horizontal, true)
        compare(control.vertical, false)
        verify(control.width > control.height)

        control.orientation = Qt.Vertical
        compare(control.orientation, Qt.Vertical)
        compare(control.horizontal, false)
        compare(control.vertical, true)
        verify(control.width < control.height)
    }

    function test_mouse_data() {
        return [
            { tag: "horizontal", orientation: Qt.Horizontal, live: false },
            { tag: "vertical", orientation: Qt.Vertical, live: false },
            { tag: "horizontal:live", orientation: Qt.Horizontal, live: true },
            { tag: "vertical:live", orientation: Qt.Vertical, live: true }
        ]
    }

    function test_mouse(data) {
        let control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation, live: data.live })
        verify(control)

        let firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(firstPressedSpy.valid)

        let firstMovedSpy = signalSpy.createObject(control, {target: control.first, signalName: "moved"})
        verify(firstMovedSpy.valid)

        let secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"})
        verify(secondPressedSpy.valid)

        let secondMovedSpy = signalSpy.createObject(control, {target: control.second, signalName: "moved"})
        verify(secondMovedSpy.valid)

        // Press and release the first handle without moving it.
        mousePress(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton)
        compare(firstPressedSpy.count, 1)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        mouseRelease(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton)
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Press and release the second handle without moving it.
        mousePress(control, control.width - control.rightPadding - 1, control.topPadding, Qt.LeftButton)
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 1)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, true)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        mouseRelease(control, control.width - control.rightPadding - 1, control.topPadding, Qt.LeftButton)
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Press and release on the bottom left corner of the control without moving the handle.
        mousePress(control, 0, control.height - 1, Qt.LeftButton)
        compare(firstPressedSpy.count, 3)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        mouseRelease(control, 0, control.height - 1, Qt.LeftButton)
        compare(firstPressedSpy.count, 4)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Drag the first handle.
        mousePress(control, control.leftPadding, control.height - control.bottomPadding - 1, Qt.LeftButton)
        compare(firstPressedSpy.count, 5)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        let horizontal = control.orientation === Qt.Horizontal
        let toX = horizontal ? control.width * 0.5 : control.first.handle.x
        let toY = horizontal ? control.first.handle.y : control.height * 0.5
        mouseMove(control, toX, toY)
        compare(firstPressedSpy.count, 5)
        compare(firstMovedSpy.count, 1)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, data.live ? 0.5 : 0.0)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, horizontal ? 1.0 : 0.0)

        mouseRelease(control, toX, toY, Qt.LeftButton)
        compare(firstPressedSpy.count, 6)
        compare(firstMovedSpy.count, 1)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.5)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, horizontal ? 1.0 : 0.0)
    }

    function test_touch_data() {
        return [
            { tag: "horizontal", orientation: Qt.Horizontal, live: false },
            { tag: "vertical", orientation: Qt.Vertical, live: false },
            { tag: "horizontal:live", orientation: Qt.Horizontal, live: true },
            { tag: "vertical:live", orientation: Qt.Vertical, live: true }
        ]
    }

    function test_touch(data) {
        let control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation, live: data.live })
        verify(control)

        let firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(firstPressedSpy.valid)

        let firstMovedSpy = signalSpy.createObject(control, {target: control.first, signalName: "moved"})
        verify(firstMovedSpy.valid)

        let secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"})
        verify(secondPressedSpy.valid)

        let secondMovedSpy = signalSpy.createObject(control, {target: control.second, signalName: "moved"})
        verify(secondMovedSpy.valid)

        // Press and release the first handle without moving it.
        let touch = touchEvent(control)
        control.setValues(0, 1);
        touch.press(0, control, control.leftPadding, control.height - control.bottomPadding - 1).commit()
        compare(firstPressedSpy.count, 1)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        touch.release(0, control, control.leftPadding, control.height - control.bottomPadding - 1).commit()
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Press and release the second handle without moving it.
        touch.press(0, control, control.width - control.rightPadding - 1, control.topPadding).commit()
        compare(firstPressedSpy.count, 2)
        compare(secondPressedSpy.count, 1)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, true)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        touch.release(0, control, control.width - control.rightPadding - 1, control.topPadding).commit()
        compare(firstPressedSpy.count, 2)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Press and release on the bottom left corner of the control without moving the handle.
        touch.press(0, control, 0, control.height - 1).commit()
        compare(firstPressedSpy.count, 3)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        touch.release(0, control, 0, control.height - 1).commit()
        compare(firstPressedSpy.count, 4)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        touch.press(0, control, control.first.handle.x, control.first.handle.y).commit()
        compare(firstPressedSpy.count, 5)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)

        // Drag the first handle.
        let horizontal = control.orientation === Qt.Horizontal
        let toX = horizontal ? control.width * 0.5 : control.first.handle.x
        let toY = horizontal ? control.first.handle.y : control.height * 0.5
        touch.move(0, control, toX, toY).commit()
        compare(firstPressedSpy.count, 5)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, true)
        compare(control.first.value, data.live ? 0.5 : 0.0)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, horizontal ? 1.0 : 0.0)

        touch.release(0, control, toX, toY).commit()
        compare(firstPressedSpy.count, 6)
        compare(secondPressedSpy.count, 2)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.5)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.second.visualPosition, horizontal ? 1.0 : 0.0)
    }

    function test_multiTouch() {
        let control1 = createTemporaryObject(sliderComponent, testCase)
        verify(control1)

        // press and move the first handle of the first slider
        let touch = touchEvent(control1)
        touch.press(0, control1, 0, 0).commit().move(0, control1, control1.width / 2, control1.height / 2).commit()
        compare(control1.first.pressed, true)
        compare(control1.first.position, 0.5)
        compare(control1.second.pressed, false)
        compare(control1.second.position, 1.0)

        // press and move the second handle of the first slider
        touch.stationary(0).press(1, control1, control1.width - 1, control1.height - 1).commit()
        touch.stationary(0).move(1, control1, control1.width / 2, control1.height / 2).commit()
        compare(control1.first.pressed, true)
        compare(control1.first.position, 0.5)
        compare(control1.second.pressed, true)
        compare(control1.second.position, 0.5)

        let control2 = createTemporaryObject(sliderComponent, testCase, {y: control1.height})
        verify(control2)

        // press and move the first handle of the second slider
        touch.stationary(0).stationary(1).press(2, control2, 0, 0).commit()
        touch.stationary(0).stationary(1).move(2, control2, control2.width / 2, control2.height / 2).commit()
        compare(control1.first.pressed, true)
        compare(control1.first.position, 0.5)
        compare(control1.second.pressed, true)
        compare(control1.second.position, 0.5)
        compare(control2.first.pressed, true)
        compare(control2.first.position, 0.5)
        compare(control2.second.pressed, false)
        compare(control2.second.position, 1.0)

        // press and move the second handle of the second slider
        touch.stationary(0).stationary(1).stationary(2).press(3, control2, control2.width - 1, control2.height - 1).commit()
        touch.stationary(0).stationary(1).stationary(2).move(3, control2, control2.width / 2, control2.height / 2).commit()
        compare(control1.first.pressed, true)
        compare(control1.first.position, 0.5)
        compare(control1.second.pressed, true)
        compare(control1.second.position, 0.5)
        compare(control2.first.pressed, true)
        compare(control2.first.position, 0.5)
        compare(control2.second.pressed, true)
        compare(control2.second.position, 0.5)

        // release the both handles of the both sliders
        touch.release(0, control1).release(1, control1).release(2, control2).release(3, control2).commit()
        compare(control1.first.pressed, false)
        compare(control1.first.position, 0.5)
        compare(control1.second.pressed, false)
        compare(control1.second.position, 0.5)
        compare(control2.first.pressed, false)
        compare(control2.first.position, 0.5)
        compare(control2.second.pressed, false)
        compare(control2.second.position, 0.5)
    }

    function test_overlappingHandles() {
        if ((Qt.platform.pluginName === "offscreen")
            || (Qt.platform.pluginName === "minimal"))
            skip("Mouse hovering not functional on offscreen/minimal platforms")

        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        // By default, we force the second handle to be after the first in
        // terms of stacking order *and* z value.
        compare(control.second.handle.z, 1)
        compare(control.first.handle.z, 0)
        control.first.value = 0
        control.second.value = 0

        // When both handles overlap, only the handle with the higher Z value
        // should be hovered.
        mouseMove(control, control.second.handle.x, control.second.handle.y)
        compare(control.second.hovered, true)
        compare(control.first.hovered, false)

        // Both are at the same position, so it doesn't matter whose coordinates we use.
        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        verify(control.second.pressed)
        compare(control.second.handle.z, 1)
        compare(control.first.handle.z, 0)

        // Move the second handle out of the way.
        mouseMove(control, control.width, control.first.handle.y)
        mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton)
        verify(!control.second.pressed)
        compare(control.second.value, 1.0)
        compare(control.second.handle.z, 1)
        compare(control.first.handle.z, 0)

        // The first handle should not be hovered.
        compare(control.first.hovered, false)

        // Move the first handle on top of the second.
        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        verify(control.first.pressed)
        compare(control.first.handle.z, 1)
        compare(control.second.handle.z, 0)

        mouseMove(control, control.width, control.first.handle.y)
        mouseRelease(control, control.width, control.first.handle.y, Qt.LeftButton)
        verify(!control.first.pressed)
        compare(control.first.handle.z, 1)
        compare(control.second.handle.z, 0)

        // The most recently pressed handle (the first) should have the higher z value.
        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        verify(control.first.pressed)
        compare(control.first.handle.z, 1)
        compare(control.second.handle.z, 0)

        mouseRelease(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        verify(!control.first.pressed)
        compare(control.first.handle.z, 1)
        compare(control.second.handle.z, 0)
    }

    function test_keys_data() {
        return [
            { tag: "horizontal", orientation: Qt.Horizontal, decrease: Qt.Key_Left, increase: Qt.Key_Right },
            { tag: "vertical", orientation: Qt.Vertical, decrease: Qt.Key_Down, increase: Qt.Key_Up }
        ]
    }

    function test_keys(data) {
        let control = createTemporaryObject(sliderComponent, testCase, { orientation: data.orientation })
        verify(control)

        let pressedCount = 0

        let firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(firstPressedSpy.valid)

        control.first.handle.forceActiveFocus()
        verify(control.first.handle.activeFocus)

        control.first.value = 0.5

        for (let d1 = 1; d1 <= 10; ++d1) {
            keyPress(data.decrease)
            compare(control.first.pressed, true)
            compare(firstPressedSpy.count, ++pressedCount)

            compare(control.first.value, Math.max(0.0, 0.5 - d1 * 0.1))
            compare(control.first.value, control.first.position)

            keyRelease(data.decrease)
            compare(control.first.pressed, false)
            compare(firstPressedSpy.count, ++pressedCount)
        }

        for (let i1 = 1; i1 <= 20; ++i1) {
            keyPress(data.increase)
            compare(control.first.pressed, true)
            compare(firstPressedSpy.count, ++pressedCount)

            compare(control.first.value, Math.min(1.0, 0.0 + i1 * 0.1))
            compare(control.first.value, control.first.position)

            keyRelease(data.increase)
            compare(control.first.pressed, false)
            compare(firstPressedSpy.count, ++pressedCount)
        }

        control.first.value = 0;
        control.stepSize = 0.25

        pressedCount = 0;
        let secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"})
        verify(secondPressedSpy.valid)

        control.second.handle.forceActiveFocus()
        verify(control.second.handle.activeFocus)

        for (let d2 = 1; d2 <= 10; ++d2) {
            keyPress(data.decrease)
            compare(control.second.pressed, true)
            compare(secondPressedSpy.count, ++pressedCount)

            compare(control.second.value, Math.max(0.0, 1.0 - d2 * 0.25))
            compare(control.second.value, control.second.position)

            keyRelease(data.decrease)
            compare(control.second.pressed, false)
            compare(secondPressedSpy.count, ++pressedCount)
        }

        for (let i2 = 1; i2 <= 10; ++i2) {
            keyPress(data.increase)
            compare(control.second.pressed, true)
            compare(secondPressedSpy.count, ++pressedCount)

            compare(control.second.value, Math.min(1.0, 0.0 + i2 * 0.25))
            compare(control.second.value, control.second.position)

            keyRelease(data.increase)
            compare(control.second.pressed, false)
            compare(secondPressedSpy.count, ++pressedCount)
        }
    }

    function test_padding() {
        // test with "unbalanced" paddings (left padding != right padding) to ensure
        // that the slider position calculation is done taking padding into account
        // ==> the position is _not_ 0.5 in the middle of the control
        let control = createTemporaryObject(sliderComponent, testCase, { leftPadding: 10, rightPadding: 20, live: false })
        verify(control)

        let firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(firstPressedSpy.valid)

        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        compare(firstPressedSpy.count, 1)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.first.visualPosition, 0.0)

        mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0)
        compare(firstPressedSpy.count, 1)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)

        mouseMove(control, control.width * 0.5, control.height * 0.5, 0)
        compare(firstPressedSpy.count, 1)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        verify(control.first.position > 0.5)
        verify(control.first.visualPosition > 0.5)

        mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton)
        compare(firstPressedSpy.count, 2)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.5)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)

        // RTL
        control.first.value = 0
        control.locale = Qt.locale("ar_EG")

        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        compare(firstPressedSpy.count, 3)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.first.visualPosition, 0.0)

        mouseMove(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, 0)
        compare(firstPressedSpy.count, 3)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)

        mouseMove(control, control.width * 0.5, control.height * 0.5, 0)
        compare(firstPressedSpy.count, 3)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        verify(control.first.position > 0.5)
        verify(control.first.visualPosition > 0.5)

        mouseRelease(control, control.leftPadding + control.availableWidth * 0.5, control.height * 0.5, Qt.LeftButton)
        compare(firstPressedSpy.count, 4)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.5)
        compare(control.first.position, 0.5)
        compare(control.first.visualPosition, 0.5)
    }

    function test_snapMode_data(immediate) {
        return [
            { tag: "NoSnap", snapMode: RangeSlider.NoSnap, from: 0, to: 2, values: [0, 0, 0.25], positions: [0, 0.1, 0.1] },
            { tag: "SnapAlways (0..2)", snapMode: RangeSlider.SnapAlways, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] },
            { tag: "SnapAlways (1..3)", snapMode: RangeSlider.SnapAlways, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] },
            { tag: "SnapAlways (-1..1)", snapMode: RangeSlider.SnapAlways, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] },
            { tag: "SnapAlways (1..-1)", snapMode: RangeSlider.SnapAlways, from: 1, to: -1, values: [0.0, 0.0,  0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] },
            { tag: "SnapOnRelease (0..2)", snapMode: RangeSlider.SnapOnRelease, from: 0, to: 2, values: [0.0, 0.0, 0.2], positions: [0.0, 0.1, 0.1] },
            { tag: "SnapOnRelease (1..3)", snapMode: RangeSlider.SnapOnRelease, from: 1, to: 3, values: [1.0, 1.0, 1.2], positions: [0.0, 0.1, 0.1] },
            { tag: "SnapOnRelease (-1..1)", snapMode: RangeSlider.SnapOnRelease, from: -1, to: 1, values: [0.0, 0.0, -0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] },
            { tag: "SnapOnRelease (1..-1)", snapMode: RangeSlider.SnapOnRelease, from: 1, to: -1, values: [0.0, 0.0,  0.8], positions: [immediate ? 0.0 : 0.5, 0.1, 0.1] }
        ]
    }

    function test_snapMode_mouse_data() {
        return test_snapMode_data(true)
    }

    function test_snapMode_mouse(data) {
        let control = createTemporaryObject(sliderComponent, testCase, {snapMode: data.snapMode, from: data.from, to: data.to, stepSize: 0.2, live: false, width: testCase.width})
        verify(control)

        control.first.value = 0
        control.second.value = data.to

        let fuzz = 0.05

        mousePress(control, control.leftPadding)
        compare(control.first.pressed, true)
        compare(control.first.value, data.values[0])
        compare(control.first.position, data.positions[0])

        mouseMove(control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2))
        compare(control.first.pressed, true)
        fuzzyCompare(control.first.value, data.values[1], fuzz)
        fuzzyCompare(control.first.position, data.positions[1], fuzz)

        mouseRelease(control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2))
        compare(control.first.pressed, false)
        fuzzyCompare(control.first.value, data.values[2], fuzz)
        fuzzyCompare(control.first.position, data.positions[2], fuzz)
    }

    function test_snapMode_touch_data() {
        return test_snapMode_data(false)
    }

    function test_snapMode_touch(data) {
        let control = createTemporaryObject(sliderComponent, testCase, {snapMode: data.snapMode, from: data.from, to: data.to, stepSize: 0.2, live: false, width: testCase.width})
        verify(control)

        control.first.value = 0
        control.second.value = data.to

        let fuzz = 0.05

        let touch = touchEvent(control)
        touch.press(0, control, control.first.handle.x, control.first.handle.y).commit()
        compare(control.first.pressed, true)
        compare(control.first.value, data.values[0])
        compare(control.first.position, data.positions[0])

        touch.move(0, control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)).commit()
        compare(control.first.pressed, true)
        fuzzyCompare(control.first.value, data.values[1], fuzz)
        fuzzyCompare(control.first.position, data.positions[1], fuzz)

        touch.release(0, control, control.leftPadding + 0.15 * (control.availableWidth + control.first.handle.width / 2)).commit()
        compare(control.first.pressed, false)
        fuzzyCompare(control.first.value, data.values[2], fuzz)
        fuzzyCompare(control.first.position, data.positions[2], fuzz)
    }

    function test_focus() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        compare(control.activeFocus, false)

        // focus is forwarded to the first handle
        control.forceActiveFocus()
        compare(control.activeFocus, true)
        compare(control.first.handle.activeFocus, true)
        compare(control.second.handle.activeFocus, false)

        // move focus to the second handle
        control.second.handle.forceActiveFocus()
        compare(control.activeFocus, true)
        compare(control.first.handle.activeFocus, false)
        compare(control.second.handle.activeFocus, true)

        // clear focus
        control.focus = false
        compare(control.activeFocus, false)
        compare(control.first.handle.activeFocus, false)
        compare(control.second.handle.activeFocus, false)

        // focus is forwarded to the second handle (where it previously was in the focus scope)
        control.forceActiveFocus()
        compare(control.activeFocus, true)
        compare(control.first.handle.activeFocus, false)
        compare(control.second.handle.activeFocus, true)
    }

    function test_hover_data() {
        return [
            { tag: "first:true", node: "first", hoverEnabled: true },
            { tag: "first:false", node: "first", hoverEnabled: false },
            { tag: "second:true", node: "second", hoverEnabled: true },
            { tag: "second:false", node: "second", hoverEnabled: false }
        ]
    }

    function test_hover(data) {
        let control = createTemporaryObject(sliderComponent, testCase, {hoverEnabled: data.hoverEnabled})
        verify(control)

        let node = control[data.node]
        compare(control.hovered, false)
        compare(node.hovered, false)

        mouseMove(control, node.handle.x + node.handle.width / 2, node.handle.y + node.handle.height / 2)
        compare(control.hovered, data.hoverEnabled)
        compare(node.hovered, data.hoverEnabled && node.handle.enabled)

        mouseMove(control, node.handle.x - 1, node.handle.y - 1)
        compare(node.hovered, false)
    }

    function test_nullHandles() {
        let control = createTemporaryObject(sliderComponent, testCase, {"second.value": 1})
        verify(control)

        verify(control.first.handle)
        verify(control.second.handle)

        control.first.handle = null
        control.second.handle = null

        mousePress(control, control.leftPadding, control.height / 2)
        verify(control.first.pressed, true)
        compare(control.second.pressed, false)

        mouseRelease(control, control.leftPadding, control.height / 2)
        compare(control.first.pressed, false)
        compare(control.second.pressed, false)

        mousePress(control, control.width - control.rightPadding - 1, control.height / 2)
        compare(control.first.pressed, false)
        compare(control.second.pressed, true)

        mouseRelease(control, control.width - control.rightPadding - 1, control.height / 2)
        compare(control.first.pressed, false)
        compare(control.second.pressed, false)
    }

    function test_touchDragThreshold_data() {
        let d1 = 3; let d2 = 7;
        return [
            { tag: "horizontal", orientation: Qt.Horizontal, dx1: d1, dy1: 0, dx2: d2, dy2: 0 },
            { tag: "vertical", orientation: Qt.Vertical, dx1: 0, dy1: -d1, dx2: 0, dy2: -d2 },
            { tag: "horizontal2", orientation: Qt.Horizontal, dx1: -d1, dy1: 0, dx2: -d2, dy2: 0 },
            { tag: "vertical2", orientation: Qt.Vertical, dx1: 0, dy1: d1, dx2: 0, dy2: d2 },
        ]
    }

    function test_touchDragThreshold(data) {
        let control = createTemporaryObject(sliderComponent, testCase, {touchDragThreshold: 10, live: true, orientation: data.orientation, "first.value": 0, "second.value": 1})
        verify(control)
        compare(control.touchDragThreshold, 10)

        let valueChangedCount = 0
        let valueChangedSpy = signalSpy.createObject(control, {target: control, signalName: "touchDragThresholdChanged"})
        verify(valueChangedSpy.valid)

        control.touchDragThreshold = undefined
        compare(control.touchDragThreshold, -1) // reset to -1
        compare(valueChangedSpy.count, ++valueChangedCount)

        let t = 5
        control.touchDragThreshold = t
        compare(control.touchDragThreshold, t)
        compare(valueChangedSpy.count, ++valueChangedCount)

        control.touchDragThreshold = t
        compare(control.touchDragThreshold, t)
        compare(valueChangedSpy.count, valueChangedCount)

        let pressedCount = 0
        let pressedCount2 = 0
        let visualPositionCount = 0
        let visualPositionCount2 = 0

        let pressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(pressedSpy.valid)
        let pressedSpy2 = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"})
        verify(pressedSpy2.valid)

        let visualPositionSpy = signalSpy.createObject(control, {target: control.first, signalName: "visualPositionChanged"})
        verify(visualPositionSpy.valid)
        let visualPositionSpy2 = signalSpy.createObject(control, {target: control.second, signalName: "visualPositionChanged"})
        verify(visualPositionSpy2.valid)

        let touch = touchEvent(control)
        control.first.value = 0.4
        control.second.value = 1
        let x0 = control.first.handle.x + control.first.handle.width * 0.5
        let y0 = control.first.handle.y + control.first.handle.height * 0.5
        touch.press(0, control, x0, y0).commit()
        compare(pressedSpy.count, ++pressedCount)
        compare(control.first.pressed, true)
        compare(visualPositionSpy.count, ++visualPositionCount)

        touch.move(0, control, x0 + data.dx1, y0 + data.dy1).commit()
        compare(pressedSpy.count, pressedCount)
        compare(control.first.pressed, true)
        compare(visualPositionSpy.count, visualPositionCount)

        touch.move(0, control, x0 + data.dx2, y0 + data.dy2).commit()
        compare(pressedSpy.count, pressedCount)
        compare(control.first.pressed, true)
        compare(visualPositionSpy.count, ++visualPositionCount)

        touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit()

        control.first.value = 0
        control.second.value = 0.6
        x0 = control.second.handle.x + control.second.handle.width * 0.5
        y0 = control.second.handle.y + control.second.handle.height * 0.5
        touch.press(0, control, x0, y0).commit()
        compare(pressedSpy2.count, ++pressedCount2)
        compare(control.second.pressed, true)
        compare(visualPositionSpy2.count, ++visualPositionCount2)

        touch.move(0, control, x0 + data.dx1, y0 + data.dy1).commit()
        compare(pressedSpy2.count, pressedCount2)
        compare(control.second.pressed, true)
        compare(visualPositionSpy2.count, visualPositionCount2)

        touch.move(0, control, x0 + data.dx2, y0 + data.dy2).commit()
        compare(pressedSpy2.count, pressedCount2)
        compare(control.second.pressed, true)
        compare(visualPositionSpy2.count, ++visualPositionCount2)
        touch.release(0, control, x0 + data.dx2, y0 + data.dy2).commit()
    }

    function test_valueAt_data() {
        return [
            { tag: "0.0..1.0", properties: { from: 0.0, to: 1.0 }, values: [0.0, 0.2, 0.5, 1.0] },
            { tag: "0..100", properties: { from: 0, to: 100 }, values: [0, 20, 50, 100] },
            { tag: "100..-100", properties: { from: 100, to: -100 }, values: [100, 60, 0, -100] },
            { tag: "-7..7", properties: { from: -7, to: 7, stepSize: 1.0 }, values: [-7.0, -4.0, 0.0, 7.0] },
            { tag: "-3..7", properties: { from: -3, to: 7, stepSize: 5.0 }, values: [-3.0, -3.0, 2.0, 7.0] },
        ]
    }

    function test_valueAt(data) {
        let control = createTemporaryObject(sliderComponent, testCase, data.properties)
        verify(control)

        compare(control.valueAt(0.0), data.values[0])
        compare(control.valueAt(0.2), data.values[1])
        compare(control.valueAt(0.5), data.values[2])
        compare(control.valueAt(1.0), data.values[3])
    }

    function test_updatePositionOnPress() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        let firstPressedSpy = signalSpy.createObject(control, {target: control.first, signalName: "pressedChanged"})
        verify(firstPressedSpy.valid)

        let firstMovedSpy = signalSpy.createObject(control, {target: control.first, signalName: "moved"})
        verify(firstMovedSpy.valid)

        let secondPressedSpy = signalSpy.createObject(control, {target: control.second, signalName: "pressedChanged"})
        verify(secondPressedSpy.valid)

        let secondMovedSpy = signalSpy.createObject(control, {target: control.second, signalName: "moved"})
        verify(secondMovedSpy.valid)

        // Touch press and release on the left corner
        control.setValues(0.2, 0.8)
        compare(control.first.value, 0.2)
        compare(control.second.value, 0.8)
        let touch = touchEvent(control)
        touch.press(0, control, 0, control.height * 0.75).commit()
        compare(firstPressedSpy.count, 1)
        compare(firstMovedSpy.count, 0)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.2)
        compare(control.first.position, 0.2)
        compare(control.second.pressed, false)
        compare(control.second.value, 0.8)
        compare(control.second.position, 0.8)

        touch.release(0, control, 0, control.height * 0.75).commit()
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 1)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 0.8)
        compare(control.second.position, 0.8)

        firstPressedSpy.clear()
        firstMovedSpy.clear()
        secondPressedSpy.clear()
        secondMovedSpy.clear()

        // Touch press and release on the right corner
        // On touch, the handle position is updated on release
        control.setValues(0.2, 0.8)
        compare(control.first.value, 0.2)
        compare(control.second.value, 0.8)
        touch = touchEvent(control)
        touch.press(0, control, control.width - control.rightPadding - 1, control.height * 0.75).commit()
        compare(secondPressedSpy.count, 1)
        compare(secondMovedSpy.count, 0)
        compare(firstPressedSpy.count, 0)
        compare(firstMovedSpy.count, 0)
        compare(control.second.pressed, true)
        compare(control.second.value, 0.8)
        compare(control.second.position, 0.8)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.2)
        compare(control.first.position, 0.2)

        touch.release(0, control, control.width - control.rightPadding - 1, control.height * 0.75).commit()
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 1)
        compare(firstPressedSpy.count, 0)
        compare(firstMovedSpy.count, 0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.2)
        compare(control.first.position, 0.2)

        firstPressedSpy.clear()
        firstMovedSpy.clear()
        secondPressedSpy.clear()
        secondMovedSpy.clear()

        // Mouse press and release on the left corner
        // On mouse, the position is immediately updated on press
        control.setValues(0.2, 0.8)
        compare(control.first.value, 0.2)
        compare(control.second.value, 0.8)
        mousePress(control, 0, control.height * 0.75, Qt.LeftButton)
        compare(firstPressedSpy.count, 1)
        compare(firstMovedSpy.count, 1)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, true)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 0.8)
        compare(control.second.position, 0.8)

        mouseRelease(control, 0, control.height * 0.75, Qt.LeftButton)
        compare(firstPressedSpy.count, 2)
        compare(firstMovedSpy.count, 1)
        compare(secondPressedSpy.count, 0)
        compare(secondMovedSpy.count, 0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.0)
        compare(control.first.position, 0.0)
        compare(control.second.pressed, false)
        compare(control.second.value, 0.8)
        compare(control.second.position, 0.8)

        firstPressedSpy.clear()
        firstMovedSpy.clear()
        secondPressedSpy.clear()
        secondMovedSpy.clear()

        // Mouse press and release on the right corner
        control.setValues(0.2, 0.8)
        compare(control.first.value, 0.2)
        compare(control.second.value, 0.8)
        mousePress(control, control.width - control.rightPadding - 1, control.height * 0.75, Qt.LeftButton)
        compare(secondPressedSpy.count, 1)
        compare(secondMovedSpy.count, 1)
        compare(firstPressedSpy.count, 0)
        compare(firstMovedSpy.count, 0)
        compare(control.second.pressed, true)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.2)
        compare(control.first.position, 0.2)

        mouseRelease(control, control.width - control.rightPadding - 1, control.height * 0.75, Qt.LeftButton)
        compare(secondPressedSpy.count, 2)
        compare(secondMovedSpy.count, 1)
        compare(firstPressedSpy.count, 0)
        compare(firstMovedSpy.count, 0)
        compare(control.second.pressed, false)
        compare(control.second.value, 1.0)
        compare(control.second.position, 1.0)
        compare(control.first.pressed, false)
        compare(control.first.value, 0.2)
        compare(control.first.position, 0.2)
    }

    function test_clickFocus() {
        let control = createTemporaryObject(sliderComponent, testCase)
        verify(control)

        // Click on the second handle - it should get focus on press.
        mousePress(control, control.second.handle.x, control.second.handle.y, Qt.LeftButton)
        if (Qt.platform.os === "osx")
            verify(!control.activeFocus)
        else
            verify(control.activeFocus)
        mouseRelease(control, control.second.handle.x, control.second.handle.y, Qt.LeftButton)

        // Click on the first handle - it should get focus on press.
        mousePress(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
        if (Qt.platform.os === "osx")
            verify(!control.activeFocus)
        else
            verify(control.activeFocus)
        mouseRelease(control, control.first.handle.x, control.first.handle.y, Qt.LeftButton)
    }

    function mouseXForPosition(control, position) {
        position = Math.min(Math.max(position, 0.0), 1.0)
        // Shouldn't matter which handle we use for the width, assuming they're both the same.
        compare(control.first.handle.width, control.second.handle.width)
        return control.leftPadding + control.first.handle.width * 0.5
            + position * (control.availableWidth - control.first.handle.width)
    }

    function test_integerStepping_data() {
        return [
            { tag: "mouse" },
            { tag: "keyboard" }
        ]
    }

    function test_integerStepping(data) {
        let control = createTemporaryObject(sliderComponent, testCase,
            { stepSize: Slider.SnapAlways })
        verify(control)

        control.from = 0
        control.to = 7
        control.stepSize = 1

        const useMouse = data.tag === "mouse"
        if (useMouse) {
            // Start a drag on the second handle, which starts at 1.
            mousePress(control, mouseXForPosition(control, 1 / control.to))
        } else {
            control.second.handle.forceActiveFocus()
            verify(control.second.handle.activeFocus)
        }

        for (let i = 1; i < control.to; ++i) {
            if (useMouse)
                mouseMove(control, mouseXForPosition(control, i / control.to))

            // Compare as strings to avoid a fuzzy compare; we want an exact match.
            compare("" + control.second.value, "" + i)

            if (!useMouse)
                keyClick(Qt.Key_Right)
        }

        if (useMouse)
            mouseRelease(control)
    }
}
